/*
 * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing.text.rtf;

import javax.swing.text.StyleConstants;
import javax.swing.text.AttributeSet;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.TabStop;
import java.util.*;
import java.io.IOException;

class RTFAttributes {

  static RTFAttribute attributes[];

  static {
    Vector<RTFAttribute> a = new Vector<RTFAttribute>();
    int CHR = RTFAttribute.D_CHARACTER;
    int PGF = RTFAttribute.D_PARAGRAPH;
    int SEC = RTFAttribute.D_SECTION;
    int DOC = RTFAttribute.D_DOCUMENT;
    int PST = RTFAttribute.D_META;
    Boolean True = Boolean.valueOf(true);
    Boolean False = Boolean.valueOf(false);

    a.addElement(new BooleanAttribute(CHR, StyleConstants.Italic, "i"));
    a.addElement(new BooleanAttribute(CHR, StyleConstants.Bold, "b"));
    a.addElement(new BooleanAttribute(CHR, StyleConstants.Underline, "ul"));
    a.addElement(NumericAttribute.NewTwips(PGF, StyleConstants.LeftIndent, "li",
        0f, 0));
    a.addElement(NumericAttribute.NewTwips(PGF, StyleConstants.RightIndent, "ri",
        0f, 0));
    a.addElement(NumericAttribute.NewTwips(PGF, StyleConstants.FirstLineIndent, "fi",
        0f, 0));

    a.addElement(new AssertiveAttribute(PGF, StyleConstants.Alignment,
        "ql", StyleConstants.ALIGN_LEFT));
    a.addElement(new AssertiveAttribute(PGF, StyleConstants.Alignment,
        "qr", StyleConstants.ALIGN_RIGHT));
    a.addElement(new AssertiveAttribute(PGF, StyleConstants.Alignment,
        "qc", StyleConstants.ALIGN_CENTER));
    a.addElement(new AssertiveAttribute(PGF, StyleConstants.Alignment,
        "qj", StyleConstants.ALIGN_JUSTIFIED));
    a.addElement(NumericAttribute.NewTwips(PGF, StyleConstants.SpaceAbove,
        "sa", 0));
    a.addElement(NumericAttribute.NewTwips(PGF, StyleConstants.SpaceBelow,
        "sb", 0));

    a.addElement(new AssertiveAttribute(PST, RTFReader.TabAlignmentKey,
        "tqr", TabStop.ALIGN_RIGHT));
    a.addElement(new AssertiveAttribute(PST, RTFReader.TabAlignmentKey,
        "tqc", TabStop.ALIGN_CENTER));
    a.addElement(new AssertiveAttribute(PST, RTFReader.TabAlignmentKey,
        "tqdec", TabStop.ALIGN_DECIMAL));

    a.addElement(new AssertiveAttribute(PST, RTFReader.TabLeaderKey,
        "tldot", TabStop.LEAD_DOTS));
    a.addElement(new AssertiveAttribute(PST, RTFReader.TabLeaderKey,
        "tlhyph", TabStop.LEAD_HYPHENS));
    a.addElement(new AssertiveAttribute(PST, RTFReader.TabLeaderKey,
        "tlul", TabStop.LEAD_UNDERLINE));
    a.addElement(new AssertiveAttribute(PST, RTFReader.TabLeaderKey,
        "tlth", TabStop.LEAD_THICKLINE));
    a.addElement(new AssertiveAttribute(PST, RTFReader.TabLeaderKey,
        "tleq", TabStop.LEAD_EQUALS));

        /* The following aren't actually recognized by Swing */
    a.addElement(new BooleanAttribute(CHR, Constants.Caps, "caps"));
    a.addElement(new BooleanAttribute(CHR, Constants.Outline, "outl"));
    a.addElement(new BooleanAttribute(CHR, Constants.SmallCaps, "scaps"));
    a.addElement(new BooleanAttribute(CHR, Constants.Shadow, "shad"));
    a.addElement(new BooleanAttribute(CHR, Constants.Hidden, "v"));
    a.addElement(new BooleanAttribute(CHR, Constants.Strikethrough,
        "strike"));
    a.addElement(new BooleanAttribute(CHR, Constants.Deleted,
        "deleted"));

    a.addElement(new AssertiveAttribute(DOC, "saveformat", "defformat", "RTF"));
    a.addElement(new AssertiveAttribute(DOC, "landscape", "landscape"));

    a.addElement(NumericAttribute.NewTwips(DOC, Constants.PaperWidth,
        "paperw", 12240));
    a.addElement(NumericAttribute.NewTwips(DOC, Constants.PaperHeight,
        "paperh", 15840));
    a.addElement(NumericAttribute.NewTwips(DOC, Constants.MarginLeft,
        "margl", 1800));
    a.addElement(NumericAttribute.NewTwips(DOC, Constants.MarginRight,
        "margr", 1800));
    a.addElement(NumericAttribute.NewTwips(DOC, Constants.MarginTop,
        "margt", 1440));
    a.addElement(NumericAttribute.NewTwips(DOC, Constants.MarginBottom,
        "margb", 1440));
    a.addElement(NumericAttribute.NewTwips(DOC, Constants.GutterWidth,
        "gutter", 0));

    a.addElement(new AssertiveAttribute(PGF, Constants.WidowControl,
        "nowidctlpar", False));
    a.addElement(new AssertiveAttribute(PGF, Constants.WidowControl,
        "widctlpar", True));
    a.addElement(new AssertiveAttribute(DOC, Constants.WidowControl,
        "widowctrl", True));

    RTFAttribute[] attrs = new RTFAttribute[a.size()];
    a.copyInto(attrs);
    attributes = attrs;
  }

  static Dictionary<String, RTFAttribute> attributesByKeyword() {
    Dictionary<String, RTFAttribute> d = new Hashtable<String, RTFAttribute>(attributes.length);

    for (RTFAttribute attribute : attributes) {
      d.put(attribute.rtfName(), attribute);
    }

    return d;
  }

  /************************************************************************/
  /************************************************************************/

  static abstract class GenericAttribute {

    int domain;
    Object swingName;
    String rtfName;

    protected GenericAttribute(int d, Object s, String r) {
      domain = d;
      swingName = s;
      rtfName = r;
    }

    public int domain() {
      return domain;
    }

    public Object swingName() {
      return swingName;
    }

    public String rtfName() {
      return rtfName;
    }

    abstract boolean set(MutableAttributeSet target);

    abstract boolean set(MutableAttributeSet target, int parameter);

    abstract boolean setDefault(MutableAttributeSet target);

    public boolean write(AttributeSet source,
        RTFGenerator target,
        boolean force)
        throws IOException {
      return writeValue(source.getAttribute(swingName), target, force);
    }

    public boolean writeValue(Object value, RTFGenerator target,
        boolean force)
        throws IOException {
      return false;
    }
  }

  static class BooleanAttribute
      extends GenericAttribute
      implements RTFAttribute {

    boolean rtfDefault;
    boolean swingDefault;

    protected static final Boolean True = Boolean.valueOf(true);
    protected static final Boolean False = Boolean.valueOf(false);

    public BooleanAttribute(int d, Object s,
        String r, boolean ds, boolean dr) {
      super(d, s, r);
      swingDefault = ds;
      rtfDefault = dr;
    }

    public BooleanAttribute(int d, Object s, String r) {
      super(d, s, r);

      swingDefault = false;
      rtfDefault = false;
    }

    public boolean set(MutableAttributeSet target) {
            /* TODO: There's some ambiguity about whether this should
               *set* or *toggle* the attribute. */
      target.addAttribute(swingName, True);

      return true;  /* true indicates we were successful */
    }

    public boolean set(MutableAttributeSet target, int parameter) {
            /* See above note in the case that parameter==1 */
      Boolean value = (parameter != 0 ? True : False);

      target.addAttribute(swingName, value);

      return true; /* true indicates we were successful */
    }

    public boolean setDefault(MutableAttributeSet target) {
      if (swingDefault != rtfDefault ||
          (target.getAttribute(swingName) != null)) {
        target.addAttribute(swingName, Boolean.valueOf(rtfDefault));
      }
      return true;
    }

    public boolean writeValue(Object o_value,
        RTFGenerator target,
        boolean force)
        throws IOException {
      Boolean val;

      if (o_value == null) {
        val = Boolean.valueOf(swingDefault);
      } else {
        val = (Boolean) o_value;
      }

      if (force || (val.booleanValue() != rtfDefault)) {
        if (val.booleanValue()) {
          target.writeControlWord(rtfName);
        } else {
          target.writeControlWord(rtfName, 0);
        }
      }
      return true;
    }
  }


  static class AssertiveAttribute
      extends GenericAttribute
      implements RTFAttribute {

    Object swingValue;

    public AssertiveAttribute(int d, Object s, String r) {
      super(d, s, r);
      swingValue = Boolean.valueOf(true);
    }

    public AssertiveAttribute(int d, Object s, String r, Object v) {
      super(d, s, r);
      swingValue = v;
    }

    public AssertiveAttribute(int d, Object s, String r, int v) {
      super(d, s, r);
      swingValue = Integer.valueOf(v);
    }

    public boolean set(MutableAttributeSet target) {
      if (swingValue == null) {
        target.removeAttribute(swingName);
      } else {
        target.addAttribute(swingName, swingValue);
      }

      return true;
    }

    public boolean set(MutableAttributeSet target, int parameter) {
      return false;
    }

    public boolean setDefault(MutableAttributeSet target) {
      target.removeAttribute(swingName);
      return true;
    }

    public boolean writeValue(Object value,
        RTFGenerator target,
        boolean force)
        throws IOException {
      if (value == null) {
        return !force;
      }

      if (value.equals(swingValue)) {
        target.writeControlWord(rtfName);
        return true;
      }

      return !force;
    }
  }


  static class NumericAttribute
      extends GenericAttribute
      implements RTFAttribute {

    int rtfDefault;
    Number swingDefault;
    float scale;

    protected NumericAttribute(int d, Object s, String r) {
      super(d, s, r);
      rtfDefault = 0;
      swingDefault = null;
      scale = 1f;
    }

    public NumericAttribute(int d, Object s,
        String r, int ds, int dr) {
      this(d, s, r, Integer.valueOf(ds), dr, 1f);
    }

    public NumericAttribute(int d, Object s,
        String r, Number ds, int dr, float sc) {
      super(d, s, r);
      swingDefault = ds;
      rtfDefault = dr;
      scale = sc;
    }

    public static NumericAttribute NewTwips(int d, Object s, String r,
        float ds, int dr) {
      return new NumericAttribute(d, s, r, new Float(ds), dr, 20f);
    }

    public static NumericAttribute NewTwips(int d, Object s, String r,
        int dr) {
      return new NumericAttribute(d, s, r, null, dr, 20f);
    }

    public boolean set(MutableAttributeSet target) {
      return false;
    }

    public boolean set(MutableAttributeSet target, int parameter) {
      Number swingValue;

      if (scale == 1f) {
        swingValue = Integer.valueOf(parameter);
      } else {
        swingValue = new Float(parameter / scale);
      }
      target.addAttribute(swingName, swingValue);
      return true;
    }

    public boolean setDefault(MutableAttributeSet target) {
      Number old = (Number) target.getAttribute(swingName);
      if (old == null) {
        old = swingDefault;
      }
      if (old != null && (
          (scale == 1f && old.intValue() == rtfDefault) ||
              (Math.round(old.floatValue() * scale) == rtfDefault)
      )) {
        return true;
      }
      set(target, rtfDefault);
      return true;
    }

    public boolean writeValue(Object o_value,
        RTFGenerator target,
        boolean force)
        throws IOException {
      Number value = (Number) o_value;
      if (value == null) {
        value = swingDefault;
      }
      if (value == null) {
                /* TODO: What is the proper behavior if the Swing object does
                   not specify a value, and we don't know its default value?
                   Currently we pretend that the RTF default value is
                   equivalent (probably a workable assumption) */
        return true;
      }
      int int_value = Math.round(value.floatValue() * scale);
      if (force || (int_value != rtfDefault)) {
        target.writeControlWord(rtfName, int_value);
      }
      return true;
    }
  }
}
